#! /usr/bin/env python
#-*- encoding:utf-8 -*-

"""PSQ

Description: 
"""
__version__  = "0.1"
__date__     = "2007-08-31"
__author__   = "Loya <loya.liu at gmail.com> "
__license__  = "Licensed under the GPL v2(http://www.opensource.org)."
__copyright__= "Copyright (C) 2007 by Loya."
#=================================================================================#


import sys,os
import urllib2
import gtk
from gtk import glade
import gobject
import threading
import ConfigParser
import tips
import pmisc
import datasource
from cfgds import DataSourceCfgImpl
from psqconf import ConfigWindow


status_stock_current="sh000001"
status_group_current="favorit"
status_stock_img_current="min"

psq_home=os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, psq_home)
os.chdir(psq_home)

glade_xml_file_path="psq.glade"
psq_icon_file_path="psq.png"

total_threads_max=20	

_data_source=None
def get_datasource():
    global _data_source
    if(_data_source==None):_data_source=DataSourceCfgImpl()
    return _data_source
    
def init_listview(listview,name_list):
    ty=[]
    for i in range(len(name_list)):
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(name_list[i], renderer, text=i)
        column.set_property("reorderable", True)
        column.set_sort_column_id(i)
        listview.append_column(column)
        ty.append(gobject.TYPE_STRING)
    m=gtk.ListStore(*ty)
    listview.set_model(m)
    
def init_listview_2(listview,name_list):
    ty=[gobject.TYPE_STRING,gobject.TYPE_STRING,gobject.TYPE_FLOAT]
    listview.append_column(create_column(name_list[0],0))
    listview.append_column(create_column(name_list[1],1))
    listview.append_column(create_column(name_list[2],2,gobject.TYPE_FLOAT,True))
    for i in range(3,len(name_list)):
        listview.append_column(create_column(name_list[i],i,gobject.TYPE_FLOAT))
        ty.append(gobject.TYPE_FLOAT)
    m=gtk.ListStore(*ty)
    listview.set_model(m)

#def column_data_proc(column, cell, m, i, cid,format,align=0.0,color=0):
def column_data_proc(column, cell, m, i, data):
    (cid,format,align,color)=data
    if(not m.iter_is_valid(i)):
        print "Invalid Iter in line 60"
        cell.set_property("text","")
        return
    v=m.get_value(i,cid)
    cell.set_property("text",format % v)
    cell.set_property("xalign",align)
    if(color==1):
        cell.set_property("foreground-set",True)
        if(v>0):cell.set_property('foreground',"red")
        else:cell.set_property('foreground',"green")

def create_column(name,i,t=gobject.TYPE_STRING,is_percent=False):
    renderer=gtk.CellRendererText()
    column = gtk.TreeViewColumn(name, renderer, text=i)
    column.set_property("reorderable", True)
    if(is_percent):column.set_cell_data_func(renderer,column_data_proc,(i,"%+0.2f%%",1.0,1))
    else:
        if(t==gobject.TYPE_STRING):column.set_cell_data_func(renderer,column_data_proc,(i,"%s",0.0,0))
        elif(t==gobject.TYPE_FLOAT):column.set_cell_data_func(renderer,column_data_proc,(i,"%0.2f",1.0,0))
        else:print t
    column.set_sort_column_id(i)
    return column

class StsConfig:
    def __init__(self,path):
        self.cfg=ConfigParser.SafeConfigParser()
        self.config_file_path=path
        print "config path %s" % path
        if(os.path.exists(self.config_file_path)):
            fin=open(self.config_file_path)
            self.cfg.readfp(fin)
            fin.close()
        
    def get_groups(self):
        if(self.cfg.has_section("group")):
            return self.cfg.options("group")
        return []
        
    def get_stock_group(self,name):
        if(self.cfg.has_section("group")):
            gl= self.cfg.get("group",name)
            if(len(gl.strip())==0):return None
            sl=gl.split(";")
            sg=StockGroup(name)
            for x in sl:
                sg.get_list().append(x.split(","))
            return sg
        else: return None
        
    def save_stock_group(self,groups):
        if(not self.cfg.has_section("group")):
            self.cfg.add_section("group")
        for group in groups:
            vg=[]
            for x in group.get_list():
                vg.append(",".join(x))
            v=";".join(vg)
            self.cfg.set("group",group.get_name(),v)
    
    def save(self):
        fout=open(self.config_file_path,"w")
        self.cfg.write(fout)
        fout.close()
            

class StockGroup:
    def __init__(self,name):
        self.name=name
        self.group=[]
    def get_name(self):
        return self.name
    def add(self,stock):
        self.group.append(stock)
    def get_list(self):
        return self.group


#data from http://finance.sina.com.cn/iframe/js/suggest.js

class Suggest:
    def __init__(self,path):
        self.word_list=[]
        self.list_all=[]
        self.list_sh=[]
        self.list_sz=[]
        self.suggest_file=path
        if(not os.path.exists(path)):
            threading.Thread(None,self._download_and_load).start()
        else:
            self._load(path)
        
    def _download_and_load(self):
        print "start download suggest.js ..."
        url=urllib2.urlopen("http://finance.sina.com.cn/iframe/js/suggest.js")
        data=url.read()
        fout=open(self.suggest_file,"w")
        fout.write(data)
        fout.close()
        print "edn download suggest.js"
        self._load(self.suggest_file)
        
    def _load(self,path):
        fin=open(path)
        data=fin.read()
        data=data.decode('gbk')
        s=data.find('="')
        e=data.rfind('"')
        lstr=data[s+2:e]
        suggest_list=lstr.split('|')
        #outf=open("a.txt","w")
        for x in suggest_list:
            if(len(x)==0):continue
            entry=x.split(':')
            key=entry[0]
            vl=entry[1].split('-')
            code=vl[0]+vl[1]
            if(key[0:2]=='sh' and '0'<=key[2] and key[2]<='9' and vl[0]=='sh'):
                self.list_all.append(code)
                self.list_sh.append(code)
            elif(key[0:2]=='sz' and '0'<=key[2] and key[2]<='9' and vl[0]=='sz'):
                self.list_all.append(code)
                self.list_sz.append(code)
            self.word_list.append((key,code,vl[2]))
            #outf.write("%s,%s,%s\n" % (key,code,vl[2]))
        #outf.close()
    
    def get_list_sh(self):
        return self.list_sh
    def get_list_sz(self):
        return self.list_sz
    def get_list_all(self):
        return self.list_all
    def lookup(self,k,n):
        rlist=[]
        for x in self.word_list:
            key=x[0]
            code=x[1]
            if(key.find(k)>-1 and len(rlist)<n):rlist.append([key,code,x[2]])
        return rlist
        
class CodeWindow:
    def __init__(self,xml,su):
        self.suggest=su
        self.guixml=xml
        self.win=self.guixml.get_widget("code_window")
        self.win.connect("key-release-event",self.check_key)
        self.ent_search=self.guixml.get_widget("ent_search")
        self.tv_suggest=self.guixml.get_widget("tv_suggest")
        init_listview(self.tv_suggest,["ShortCut","Code","Name"])
        
    def check_key(self,w,e):
        #65307-ESC,65293=Enter,65288=BACK
        kv=e.keyval
        if(e.keyval==65307):self.win.hide()
        elif(e.keyval==65293):self.fire_code()
        elif((kv>=97 and kv<=122)  or ((kv>=65 and kv<=90)) or (kv>=48 and kv<=57) or kv==65288):
            self.popup()
        else:pass
        return False
        
    def fire_code(self):
        sel=self.tv_suggest.get_selection()
        (m,i)=sel.get_selected()
        self.win.hide()
        if(len(m)<1):return
        if(i==None):st=[m[0][1],m[0][2]]
        else:st=[m.get_value(i,1),m.get_value(i,2)]
        self.callback(st)
        
    def popup(self):
        code=self.ent_search.get_text()
        code_list=self.suggest.lookup(code,30)
        m=self.tv_suggest.get_model()
        if(m==None):
            m= gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
            self.tv_suggest.set_model(m)
            m=self.tv_suggest.get_model()
        #for i in range(len(m)):del m[0]
        m.clear()
        for x in code_list:m.append(x)
    
    def show(self):
        self.win.show()
        self.ent_search.grab_focus()
        self.ent_search.select_region(1,1)
        
    def hide(self,widget):
        self.win.hide()
        
    def find_code(self,func,k):
        self.callback=func
        self.ent_search.set_text(k)
        self.show()
        self.popup()
    
    def lookup(self,k,n):
        return self.suggest.lookup(k,n)
        
        
class MainWindow:
    def __init__(self):
        self.fake_stock_data=datasource.StockQuotation('sh000001')
        self.config_path="~/.psq"
        
        self.config_path=os.path.expanduser(self.config_path)
        if(not os.path.isdir(self.config_path)):os.mkdir(self.config_path)
        self.cache_path=os.path.join(self.config_path,"cache")
        if(not os.path.isdir(self.cache_path)):os.mkdir(self.cache_path)
        
        print os.path.join(self.config_path,"config")
        self.cfg=StsConfig(os.path.join(self.config_path,"config"))
        
        self.guixml=glade.XML(glade_xml_file_path)
        self.win = self.guixml.get_widget("main")
        self.win.set_position(gtk.WIN_POS_CENTER_ALWAYS)
        self.win.set_icon_from_file(psq_icon_file_path)
        self.win.connect("destroy",self.on_quit)
        
        self.guixml.get_widget("menu_quit").connect("activate",self.on_quit)
        self.guixml.get_widget("menu_about").connect("activate",self.on_about)
        
        self.win.connect("key-release-event",self.check_key)
        self.win.show()
        
        self.code_table=Suggest(os.path.join(self.config_path,"suggest.js"))
        self.code_window=CodeWindow(self.guixml,self.code_table)
        
        self.tray=tips.Tips(self.win)
        self.tray.set_tips("PSQ")
        
        #TreeView 控件
        self.tv_history=self.guixml.get_widget("tv_history")
        self.tv_favorit=self.guixml.get_widget("tv_favorit")
        self.tv_list=self.guixml.get_widget("tv_list")
        self.tv_list_sh=self.guixml.get_widget("tv_list_sh")
        self.tv_list_sz=self.guixml.get_widget("tv_list_sz")
        
        init_listview(self.tv_history,["代码","名字"])
        init_listview(self.tv_favorit,["代码","名字"])
        column_name_list=["代码","名字","涨跌幅","最新","涨跌额","昨收盘","今开盘","最高","最低","竞买","竞卖","交易量"]#,"交易额"]
        init_listview_2(self.tv_list,column_name_list)
        init_listview_2(self.tv_list_sh,column_name_list)
        init_listview_2(self.tv_list_sz,column_name_list)
        self.tv_history.connect("row-activated",self.on_row_select)
        self.tv_favorit.connect("row-activated",self.on_row_select)
        self.tv_list.connect("row-activated",self.on_row_select)
        self.tv_list_sh.connect("row-activated",self.on_row_select)
        self.tv_list_sz.connect("row-activated",self.on_row_select)
        self.tv_favorit.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        
        self.guixml.get_widget("tab_stock_group").set_current_page(1)
        self.tab_detail=self.guixml.get_widget("tab_detail")
        self.tab_detail.connect("switch-page",self.on_switch_page)
        self.toolbar_main_tooltips=gtk.Tooltips()
        tbtn_refresh=self.guixml.get_widget("tbtn_main_refresh")
        tbtn_refresh.connect("clicked",self.on_refresh)
        tbtn_refresh.set_tooltip(self.toolbar_main_tooltips,"立即刷新数据")
        img_type_list=[("min","分钟"),("daily","日K"),("weekly","周K"),("monthly","月K"),("macd","MACD"),("kdj","KDJ"),("wr","W&R")]
        tb_img=self.guixml.get_widget("tb_img")
        for i in range(0,len(img_type_list)):
            (img_type,label)=img_type_list[i]
            #btn=gtk.ToolButton(icon_widget=None,label=label)
            #btn.set_label(label)
            #btn.connect("clicked",self.on_img,img_type)
            #tb_img.add(btn)
            tb_img.append_item(label,label,label,None,self.on_img,img_type)
        self.tbtn_main_auto_refresh=self.guixml.get_widget("tbtn_main_auto_refresh")
        self.tbtn_main_auto_refresh.connect("toggled",self.on_auto)
        self.tbtn_main_auto_refresh.set_tooltip(self.toolbar_main_tooltips,"自动刷新数据(间隔为5秒)")
        self.tbtn_main_config=self.guixml.get_widget('tbtn_main_config')
        self.tbtn_main_config.connect('clicked',self.on_config)
        tbtn_main_remove_favorit=self.guixml.get_widget("tbtn_main_remove_favorit")
        tbtn_main_remove_favorit.connect("clicked",self.on_remove_favorit)
        tbtn_main_remove_favorit.set_tooltip(self.toolbar_main_tooltips,"把选择的股票从自选中删除")
        tbtn_main_add_favorit=self.guixml.get_widget("tbtn_main_add_favorit")
        tbtn_main_add_favorit.connect("clicked",self.on_add_favorit)
        tbtn_main_add_favorit.set_tooltip(self.toolbar_main_tooltips,"把当前股票加入自选")
        self.load_config()
        self.lock_update_gui_img=threading.Lock()
        self.lock_update_gui_hq=threading.Lock()
        self.lock_update_gui_list=[threading.Lock(),threading.Lock(),threading.Lock()]
        cfg_dic={'conn':{'qt_single':'sina','qt_multi':'sina','qt_img':'sina'}}
        self.config_window=ConfigWindow(self.guixml,cfg_dic,get_datasource())
    
    def on_config(self,w):
    	self.config_window.show()
        
    def on_switch_page(self,w,n,np):
        #if(np==1):
        print "switch-page %d",np
        if(np==1):
            if(len(self.tv_list.get_model())>0):return
        elif(np==2):
            if(len(self.tv_list_sh.get_model())>0):return
        elif(np==3):
            if(len(self.tv_list_sz.get_model())>0):return
        self.refresh_all(np)
    
    def close_about(self,w,rid,b):
            w.hide()
            
    def on_about(self,w):
        win_about=self.guixml.get_widget("about_window")
        win_about.connect("response",self.close_about,win_about)
        win_about.set_version("0.1")
        win_about.show()
        
    def on_add_favorit(self,w):
        name=self.code_window.lookup(status_stock_current,1)[0][2]
        self.tv_favorit.get_model().append([status_stock_current,name])
        
    def on_remove_favorit(self,w):
        sel=self.tv_favorit.get_selection()
        (m,pathlist)=sel.get_selected_rows()
        while(pathlist<>None and len(pathlist)>0):
            del m[pathlist[0][0]]
            (m,pathlist)=sel.get_selected_rows()
        
        
    def on_quit(self,w):
        self.tbtn_main_auto_refresh.set_active(False)
        self.win.hide()
        m=self.tv_favorit.get_model()
        sg=StockGroup("favorit")
        for x in m:
            sg.get_list().append(x)
        self.cfg.save_stock_group([sg])
        self.cfg.save()
        gtk.main_quit(w)
        exit(0)
        
    def load_config(self):
        sg=self.cfg.get_stock_group("favorit")
        if(sg==None):return
        m=self.tv_favorit.get_model()
        for g in sg.get_list():
            m.append(g)
    
    def on_timer(self):
        if(not self.tbtn_main_auto_refresh.get_active()):return
        if(not self.tray.visible):
			print "refresh tips of the tray only"
			m=self.tv_favorit.get_model()
			sl=[]
			for x in m:sl.append(x[0])
			sdl=get_datasource().get_stock_quotation(sl)
			self.tray.show_stocks_on_tips(sdl)
        else:
			self.refresh_all(-1,True)
        self.timer=pmisc.create_timer(5.0,self.on_timer)

        
    def on_auto(self,w):
        if(w.get_active()):
            w.set_icon_name("gtk_media_pause")
            self.on_timer()
        else:	w.set_icon_name("gtk_media_play")
            
    def on_refresh(self,w):
        self.refresh_all()
        
    def refresh_all(self,page_id=-1,auto_refresh=False): 
        global status_stock_current,status_stock_img_current
        if(page_id==-1):pgid=self.tab_detail.get_current_page()
        else:pgid=page_id
        print "pgid=%d" % pgid
        if(pgid==0):
            print "refresh %s:" % status_stock_current
            pmisc.create_thread(self.refresh_img,"ds_retr_img",True,(status_stock_current,status_stock_img_current,),None)
            pmisc.create_thread(self.refresh_hq,"ds_retr_hq",True,(status_stock_current,),None)
        else:
            if(pgid>1 and auto_refresh):
                print "page %d can't be auto refreshed, must use F5." % pgid
                return 
            print "refresh list %d" % pgid
            pmisc.create_thread(self.refresh_list,"ds_retr_list",True,(pgid,))
        
        
    def refresh_list(self,pgid=1):
        fm=self.tv_favorit.get_model()
        if(pgid==1):
            cl=[]
            for x in fm:
                cl.append(x[0])
            self._refresh_list(self.tv_list,cl,pgid)
        elif(pgid==2):self._refresh_list(self.tv_list_sh,self.code_table.get_list_sh(),pgid)
        elif(pgid==3):self._refresh_list(self.tv_list_sz,self.code_table.get_list_sz(),pgid)
        
    def _refresh_list(self,listview,code_list,page_id):
        m=listview.get_model()
        if(len(m)==0):
             pmisc.update_liststore(listview,[self.fake_stock_data])
        sdl=get_datasource().get_stock_quotation(code_list)
        if(page_id==1):self.tray.show_stocks_on_tips(sdl)
        if(len(code_list)>0):
            m=listview.get_model()
            pmisc.update_liststore(listview,sdl)
        print "refresh list end"
            
    def on_row_select(self,tv,item,vc):
        global status_stock_current
        m=tv.get_model()
        code=m[item[0]][0]
        status_stock_current=code
        if(self.tab_detail.get_current_page()<>0):self.tab_detail.set_current_page(0)
        else:self.refresh_all()
        
    def check_key(self,widget,event):
        print event.keyval
        kv= event.keyval
        k=gtk.gdk.keyval_name(event.keyval)
        #97 - a,65 - A,122-z,90-Z,48-0,57-9,65474-F5
        if(kv==65474):
            self.refresh_all()
            return False
        if((kv>=97 and kv<=122)  or ((kv>=65 and kv<=90)) or (kv>=48 and kv<=57)):
            self.code_window.find_code(self.on_show_code,k)
        return False
    
    def on_show_code(self,stock):
        global status_stock_current
        self.add_to_history(stock)
        status_stock_current=stock[0]
        self.tab_detail.set_current_page(0)
        self.refresh_all()
        
    def add_to_history(self,stock):
        m=self.tv_history.get_model()
        for x in m:
            if(stock[0]==x[0]):return
        self.tv_history.get_model().append(stock)
        
    def on_img(self,w,img_type):
        print img_type
        global status_stock_img_current
        status_stock_img_current=img_type
        threading.Thread(None,self.refresh_img,"ds_retr_img",(status_stock_current,img_type,),None).start()
        #self.refresh_all()
        
    def refresh_img(self, stockid,img_type):
        file_name =  os.path.join(self.cache_path, stockid + img_type + ".gif")        
        img=self.guixml.get_widget("img_stock")
        img.set_from_file(file_name)
        print "start download img ..."
        get_datasource().get_img_to_file(stockid,img_type,file_name)
        print "end download img"
        if(stockid<>status_stock_current or img_type<>status_stock_img_current):
            print "img have changed"
            return
        img = self.guixml.get_widget("img_stock")
        if(img<>None):img.set_from_file(file_name)
               
    def refresh_hq(self,stock):
        print stock
        if(stock<>self.guixml.get_widget("lbl_stock_code").get_text()):
            self.fake_stock_data.name="正在刷新..."
            self.fake_stock_data.code=stock
            self._show_hq(self.fake_stock_data)
        else:self.guixml.get_widget("lbl_stock_name").set_text(self.guixml.get_widget("lbl_stock_name").get_text()+"...")
        self._refresh_hq(stock)
        
    def _show_hq(self,sd):
        self.guixml.get_widget("lbl_price_now").set_text("%0.2f" % sd.price_now)
        self.guixml.get_widget("lbl_stock_price_now").set_text("%0.2f" % sd.price_now)
        self.guixml.get_widget("lbl_price_change").set_text("%0.2f" % sd.change)
        self.guixml.get_widget("lbl_price_change_percent").set_text("%0.2f%%" % sd.change_percent)
        self.guixml.get_widget("lbl_price_last").set_text("%0.2f" % sd.price_lastday)
        self.guixml.get_widget("lbl_price_highest").set_text("%0.2f" % sd.price_highest)
        self.guixml.get_widget("lbl_price_lowest").set_text("%0.2f" % sd.price_lowest)
        self.guixml.get_widget("lbl_stock_name").set_text("%s" % sd.name)
        self.guixml.get_widget("lbl_stock_code").set_text("%s" % sd.code)
        self.guixml.get_widget("lbl_stock_update_time").set_text("(%s)" % sd.time)
        for i in range(0,5):
            self.guixml.get_widget("lbl_sale_price_%d" % (5-i)).set_text("%0.2f" % sd.slot_sale[i][0])
            self.guixml.get_widget("lbl_sale_amount_%d" % (5-i)).set_text("%0.0f" % (sd.slot_sale[i][1]/100))
            self.guixml.get_widget("lbl_buy_price_%d" % (i+1)).set_text("%0.2f" % sd.slot_buy[i][0])
            self.guixml.get_widget("lbl_buy_amount_%d" % (i+1)).set_text("%0.0f" % (sd.slot_buy[i][1]/100))
            
    def _refresh_hq(self,stock):
        sdl=get_datasource().get_stock_quotation([stock])
        if(len(sdl)>0 and stock==status_stock_current):self._show_hq(sdl[0])
            
    def main(self):
        gtk.gdk.threads_init()
        gtk.main()

        
if __name__=="__main__":
    main_window=MainWindow()
    main_window.main()
